#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include "chat-server.h"
#include "hashtable.h"
#include "io.h"
#include "messaggio.h"

//TabellaHash inizializzata a NULL
hashcell * Table[HL] = { NULL };

//Variabile globale per l'utilizzo del buffer circolare per i worker e il dispatcher
circolare * buffer;

//Variabili globali che contengono i percorsi dei file
char userfile[MAX_CHAR * 4], logfile[MAX_CHAR * 4];

//Puntatore globale al log-file e al socket master
int logdescr, masterdescr;

//Variabile globale go per il controllo dei vari cicli dei thread
int go = 1;

//Variabile globale numThreadAttivi per il controllo del numero dei thread worker attivi
int numThreadAttivi = 0;

//Stringa utilizzata per il timestamp
char timestring[64];

//Dichiaro variabili globali per contenere riferimenti ai thread worker e al dispatcher
pthread_t worker_tid, dispatcher_tid;
pthread_attr_t attr;

//Dichiaro alcuni mux utili per la gestione della hashtable e il logfile in mutua esclusione
pthread_mutex_t logmux, hashmux;

/*
 * Procedura principale che viene eseguita dal server
 *
 * @param Intero contenente il numero delle opzioni al momento dell'avvio
 * @param Puntatore a vettore di caratteri contenente tutte le opzioni di avvio
 */
int main(int argc, char *argv[]) {

	//Variabile intera usata per il salvataggio del Process ID
	int pid;

	//Variabile di tipo pthread_t che contiene un riferimento per il thread main
	pthread_t main_tid;

	//Alloco la struttura per il buffer circolare
	buffer = malloc(sizeof(circolare));

	//Se il numero dei parametri è esatto avvio il server aprendo i file
	if(argc - 1 != 2) {

		fprintf(stderr, "Errore durante l'avvio del server, parametri mancanti\n");
		exit(-1);

	//Se il numero dei parametri è corretto proseguo con l'avvio del server
	} else {

		//Copio il valore dei parametri inseriti al momento dell'esecuzione
		strcpy(userfile, argv[1]);
		strcpy(logfile, argv[2]);

		//Creo il processo figlio
		pid = fork();

		//Se il figlio è creato con successo avvio il thread main
		if(pid == 0) {

			//Avvio il thread main
			if(pthread_create(&main_tid, NULL, threadMain, NULL) < 0)
				fprintf(stderr, "Errore durante la creazione del thread main, terminazione del server...\n");

			//Se il thread è avviato con successo, attendo che termini per proseguire con l'esecuzione
			else
				pthread_join(main_tid, NULL);

		//Se il figlio non è stato creato con successo stampo l'errore
		} else if(pid < 0){

			//Stampo il messaggio di errore
			fprintf(stderr, "Errore durante la creazione del processo figlio, terminazione del server...\n");
			exit(-1);
		}
	}

	//Termino l'esecuzione
	exit(0);
}


/*
 * Procedura eseguita dal thread main come da specifiche
 *
 * @param Puntatore a void non utilizzato
 */
void *threadMain(void *argv) {

	//Stringa per il salvataggio delle cose da scrivere nel file
	char tobewrite[MAX_CHAR];

	//Intero per salvare il risultato delle scritture (Evito warning)
	int result = 0;

	//Dichiaro delle variabili per la gestione del socket
	struct sockaddr_in master;
	int clientdescr;

	//Sopprime warning durante la compilazione
	result = result;

	/* ****************************************************************** *
	 * Fase di inizializzazione file e socket                             *
	 * ****************************************************************** */

	//Inizializzo il buffer
	buffer -> readpos = 0;
	buffer -> writepos = 0;
	buffer -> cont = 0;
	pthread_mutex_init(&buffer -> mux, NULL);
	pthread_cond_init(&buffer -> empty, NULL);
	pthread_cond_init(&buffer -> empty, NULL);

	//Inizializzo i mux per le altre mutue esclusioni
	pthread_mutex_init(&logmux, NULL);
	pthread_mutex_init(&hashmux, NULL);

	//Preparo il thread main per la gestione dei segnali
	signal(SIGINT, gestoreSegnali);
	signal(SIGTERM, gestoreSegnali);

	//Leggo user-file per caricare gli utenti nella tabellaHash
	leggiFile(Table, userfile);

	//Apro il log-file in scrittura cancellando il resto del file
	logdescr = open(logfile, O_CREAT | O_WRONLY | O_TRUNC, 777);

	//Controllo se il file è stato aperto con successo
	if (logdescr < 0) {
		fprintf(stderr, "Errore durante l'apertura del file, terminazione del server...\n");
		pthread_exit(NULL);
	}

	//Ricavo il timestamp e lo preparo alla scrittura del file di log
	timestamp(timestring);

	//Scrivo l'instestazione del file di log
	strcpy(tobewrite, "****************************************************\n");
	result = write(logdescr, tobewrite, strlen(tobewrite));
	strcpy(tobewrite, "** Chat Server started @ ");
	result = write(logdescr, tobewrite, strlen(tobewrite));
	result = write(logdescr, timestring, strlen(timestring));
	strcpy(tobewrite, " **\n");
	result = write(logdescr, tobewrite, strlen(tobewrite));
	strcpy(tobewrite, "****************************************************\n");
	result = write(logdescr, tobewrite, strlen(tobewrite));

	//Azzero la struttura contenente i parametri del socket
	bzero((char*) &master, sizeof(struct sockaddr_in));

	//Definisco i parametri del socket
	master.sin_family = AF_INET;
	master.sin_port = htons(12345);
	master.sin_addr.s_addr = INADDR_ANY;

	//Creo il socket e ne salvo il descrittore in descr
	masterdescr = socket(AF_INET, SOCK_STREAM, 0);

	/* ****************************************************************** *
	 * Controlli vari sul socket e avvio dei thread                       *
	 * ****************************************************************** */

	//Controllo che il socket sia stato creato senza problemi
	if(masterdescr < 0) {
		fprintf(stderr, "Errore durante l'apertura del socket, terminazione del server...\n");
		timestamp(timestring);
		strcpy(tobewrite, timestring);
		strcat(tobewrite, "Errore durante l'apertura del socket, terminazione del server...\n");
		result = write(logdescr, tobewrite, strlen(tobewrite));
		pthread_exit(NULL);
	}

	/*//Indico al kernel che di riassegnare la porta (se già in utilizzo)
	int yes = 1;
	if(setsockopt(masterdescr, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0) {
		fprintf(stderr, "Errore durante la reassegnazione del socket, terminazione del server...\n");
		timestamp(timestring);
		strcpy(tobewrite, timestring);
		strcat(tobewrite, "Errore durante la reassegnazione del socket, terminazione del server...\n");
		result = write(logdescr, tobewrite, strlen(tobewrite));
		pthread_exit(NULL);
	}*/

	//Controllo che il socket sia stato assegnato senza problemi
	if(bind(masterdescr, (struct sockaddr*) &master, sizeof(master)) < 0) {
		fprintf(stderr, "Errore durante l'assegnazione del socket (processo già aperto?), terminazione del server...\n");
		timestamp(timestring);
		strcpy(tobewrite, timestring);
		strcat(tobewrite, "Errore durante l'assegnazione del socket, terminazione del server...\n");
		result = write(logdescr, tobewrite, strlen(tobewrite));
		pthread_exit(NULL);
	}

	//Controllo che sia possibile mettersi in attesa di connessione con una coda di grandezza 10
	if(listen(masterdescr, 10) < 0) {
		fprintf(stderr, "Errore durante l'attesa di connessione, terminazione del server...\n");
		timestamp(timestring);
		strcpy(tobewrite, timestring);
		strcat(tobewrite, "Errore durante l'attesa di connessione, terminazione del server...\n");
		result = write(logdescr, tobewrite, strlen(tobewrite));
		pthread_exit(NULL);
	}

	//Controllo che sia possibile avviare il thread dispatcher
	if(pthread_create(&dispatcher_tid, NULL, threadDispatcher, NULL) < 0) {
		fprintf(stderr, "Errore durante la creazione del thread dispatcher, terminazione del server...\n");
		timestamp(timestring);
		strcpy(tobewrite, timestring);
		strcat(tobewrite, "Errore durante l'attesa di connessione, terminazione del server...\n");
		result = write(logdescr, tobewrite, strlen(tobewrite));
		pthread_exit(NULL);
	}

	//Specifico che i tread creati, con l'uso di attr, oltre questo punto siano in modalità detached
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

	//Se non ci sono stati problemi in precedenza avvio il ciclo con la variabile globale go
	while(go) {

		//Ottengo il descrittore del socket client
		clientdescr = accept(masterdescr, NULL, 0);

		//Controllo se il thread worker può essere creato
		if(pthread_create(&worker_tid, &attr, threadWorker, (void*) &clientdescr) < 0) {
			fprintf(stderr, "Errore durante la creazione di un thread worker\n");

			//Scrivo sul file di log in mutua esclusione
			pthread_mutex_lock(&logmux);
			timestamp(timestring);
			strcpy(tobewrite, timestring);
			strcat(tobewrite, "Errore durante la creazione di un thread worker\n");
			result = write(logdescr, tobewrite, strlen(tobewrite));
			pthread_mutex_unlock(&logmux);

		//Se il thread_worker è stato creato aggiorno la quantità thread attivi
		} else {
			numThreadAttivi++;
		}
	}

	//Attendo la terminazione del thread dispatcher
	pthread_join(dispatcher_tid, NULL);

	//Elimino la struttura attr utilizzata in precedenza
	pthread_attr_destroy(&attr);

	//Termino l'esecuzione del thread main
	pthread_exit(NULL);
	exit(0);
}


/*
 * Procedura eseguita dal thread writer come da specifiche
 *
 * @param Puntatore a void non utilizzato
 */
void *threadDispatcher(void *argv) {

	//Variabili utili per la lettura dal buffer
	char * temp = malloc(sizeof(char));
	int descr, size, i, * list = malloc(sizeof(int));
	messaggio * msx = malloc(sizeof(messaggio));
	hashcell * elemento;

	//Eseguo il ciclo della variabile globale go
	while(go) {

		/* ****************************************************************** *
		 * Controllo il buffer circolare per estrarre messaggi                *
		 * ****************************************************************** */

		//Resetto il messaggio per preparare la struttura ad essere scritta
		resettaMessaggio(msx);

		//Preparo per la lettura dal buffer
		pthread_mutex_lock(&buffer -> mux);
		while (buffer -> cont == 0) {
			pthread_cond_wait(&buffer -> empty, &buffer -> mux);
			if(go == 0)
				pthread_exit(NULL);
		}

		//Leggo il messaggio dal buffer
		free(temp);
		temp = malloc(sizeof(char) * (strlen(buffer -> space[buffer -> readpos])));
		strcpy(temp ,buffer -> space[buffer -> readpos]);
		free(buffer -> space[buffer -> readpos]);
		buffer -> space[buffer -> readpos] = NULL;
		buffer -> cont--;
		buffer -> readpos++;

		//Gestione circolare posizione di lettura
		if(buffer -> readpos >= BUFFER_SIZE)
			buffer -> readpos = 0;

		//Risveglio dei worker se ce ne sono in attesa di poter scrivere il buffer
		pthread_cond_signal(&buffer -> full);
		pthread_mutex_unlock(&buffer -> mux);

		//Ricavo il messaggio dal buffer
		depacchettaMessaggio(msx, temp);

		/* ****************************************************************** *
		 * Determino il tipo di messaggio letto (messaggio a singolo)         *
		 * ****************************************************************** */

		//Determino le operazioni da eseguire in base al tipo di messaggio
		switch(msx -> type) {

		//Caso di messaggio singolo
		case MSG_SINGLE:

			//Ottengo il descrittore del socket del destinatario
			pthread_mutex_lock(&hashmux);
			elemento = ricercaUsername(Table, msx -> receiver);

			//Controllo che il client non sia andato offline prima della ricezione del messaggio
			if(elemento != NULL) {
				descr = elemento -> descr;
				pthread_mutex_unlock(&hashmux);

				//Preparo il messaggio per l'invio
				msx -> receiver = NULL;
				free(temp);
				temp = malloc(grandezzaMessaggio(msx));
				impacchettaMessaggio(msx, temp);
				size = strlen(temp);

				//Invio il messaggio
				if((write(descr, &size, sizeof(int)) < 0) || (write(descr, temp, strlen(temp) + 1) < 0))

					//Eseguo la procedura di disconnessione se ci sono stati errori
					disconnesso(descr);
			}
			break;

		/* ****************************************************************** *
		 * Determino il tipo di messaggio letto (messaggio broadcast)         *
		 * ****************************************************************** */

		//Caso di messaggio singolo
		case MSG_BRDCAST:

			//Ottengo il descrittore del socket dei destinatari
			free(list);
			list = malloc(sizeof(int) * numThreadAttivi);
			pthread_mutex_lock(&hashmux);
			listaDescrittoriOnline(Table, list, numThreadAttivi);
			pthread_mutex_unlock(&hashmux);

			//Invio il messaggio a tutti i destinatari
			for(i = 0; i < numThreadAttivi; i++) {

				//Preparo il messaggio per l'invio
				msx -> receiver = NULL;
				free(temp);
				temp = malloc(grandezzaMessaggio(msx));
				impacchettaMessaggio(msx, temp);
				size = strlen(temp);

				//Invio il messaggio
				if((write(list[i], &size, sizeof(int)) < 0) || (write(list[i], temp, strlen(temp) + 1) < 0))

					//Eseguo la procedura di disconnessione se ci sono stati errori
					disconnesso(list[i]);
			}
			break;
		}
	}

	//Termino l'esecuzione del thread dispatcher
	pthread_exit(NULL);
}


/*
 * Procedura eseguita dal thread writer come da specifiche
 *
 * @param Puntatore a void utilizzato per il descrittore del socket su cui lavorare
 */
void *threadWorker(void *argv) {

	//Ottengo il descrittore del socket dal parametro
	int descr = *(int*) argv;

	//Stringa per il salvataggio delle cose da scrivere nel file
	char tobewrite[MAX_CHAR * 8];

	//Dichiaro alcune variabili utili alla lettura da socket
	int size = 0, result = 0, i = 0;
	char username[MAX_CHAR], name[MAX_CHAR], email[MAX_CHAR], receiver[MAX_CHAR];
	char * temp = malloc(sizeof(char));
	char * list = malloc(sizeof(char));
	messaggio * msx = malloc(sizeof(messaggio));
	hashcell * elemento;

	//Sopprime warning durante la compilazione
	result = result;

	//Eseguo il ciclo della variabile globale go
	while(go) {

		//Resetto il messaggio per preparare la struttura ad essere scritta
		resettaMessaggio(msx);

		//Controllo se è possibile leggere dal socket
		if(read(descr, &size, sizeof(int)) <= 0)
			disconnesso(descr);

		//Dealloco temp per poter scegliere la dimensione della memoria per la lettura
		free(temp);

		//Alloco una quantità necessaria per contenere il messaggio
		temp = malloc(sizeof(char) * size);

		//Controllo se è possibile leggere dal socket
		if(read(descr, temp, size + 1) <= 0)
			disconnesso(descr);

		//Depacchetto il messaggio ricevuto
		depacchettaMessaggio(msx, temp);

		/* ****************************************************************** *
		 * Determino il tipo di messaggio letto (messaggio login)             *
		 * ****************************************************************** */

		//Determino le operazioni da eseguire in base al tipo di messaggio
		switch(msx -> type) {

		//Caso di login
		case MSG_LOGIN:

			//Controllo se l'utente può connettersi
			pthread_mutex_lock(&hashmux);
			elemento = ricercaUsername(Table, msx -> msg);
			strcpy(username, msx -> msg);

			//Controllo se l'utente esiste
			if(elemento == NULL) {

				//Compilo il messaggio di risposta
				pthread_mutex_unlock(&hashmux);
				resettaMessaggio(msx);
				msx -> type = MSG_ERROR;
				msx -> msglen = strlen("Impossibile connettersi, registrarsi prima...");
				msx -> msg = malloc(msx -> msglen + 1);
				strcpy(msx -> msg, "Impossibile connettersi, registrarsi prima...");
				impacchettaMessaggio(msx, temp);

				//Invio la risposta al client
				size = strlen(temp);
				result = write(descr, &size, sizeof(int));
				result = write(descr, temp, strlen(temp) + 1);

				//Diminuisco il numero di thread attivi, chiudo il socket e termino il thread
				numThreadAttivi--;
				close(descr);
				pthread_exit(NULL);

			//Se l'utente esiste, controllo che non sia già connesso
			} else if(elemento -> descr != -1) {

				//Compilo il messaggio di risposta
				pthread_mutex_unlock(&hashmux);
				resettaMessaggio(msx);
				msx -> type = MSG_ERROR;
				msx -> msglen = strlen("Impossibile connettersi, utente già online...");
				msx -> msg = malloc(msx -> msglen + 1);
				strcpy(msx -> msg, "Impossibile connettersi, utente già online...");
				impacchettaMessaggio(msx, temp);

				//Invio la risposta al client
				size = strlen(temp);
				result = write(descr, &size, sizeof(int));
				result = write(descr, temp, strlen(temp) + 1);

				//Diminuisco il numero di thread attivi, chiudo il socket e termino il thread
				numThreadAttivi--;
				close(descr);
				pthread_exit(NULL);

			//Se l'utente può connettersi
			} else {

				//Aggiorno la tabellaHash
				elemento -> descr = descr;

				//Compilo il messaggio di risposta
				pthread_mutex_unlock(&hashmux);
				resettaMessaggio(msx);
				msx -> type = MSG_OK;

				//Scrivo nel log l'evento
				pthread_mutex_lock(&logmux);
				timestamp(timestring);
				strcpy(tobewrite, timestring);
				strcat(tobewrite, ":");
				strcat(tobewrite, "login:");
				strcat(tobewrite, username);
				strcat(tobewrite, "\n");
				result = write(logdescr, tobewrite, strlen(tobewrite));
				pthread_mutex_unlock(&logmux);
			}

			//Preparo il messaggio per l'invio
			free(temp);
			temp = malloc(grandezzaMessaggio(msx));
			impacchettaMessaggio(msx, temp);
			size = strlen(temp);

			//Invio il messaggio
			if((write(descr, &size, sizeof(int)) < 0) || (write(descr, temp, strlen(temp) + 1) < 0))

				//Eseguo la procedura di disconnessione se ci sono stati errori
				disconnesso(descr);
			break;

		/* ****************************************************************** *
		 * Determino il tipo di messaggio letto (messaggio register & login)  *
		 * ****************************************************************** */

		//Caso di registrazione
		case REG_LOGIN:

			//Ricavo i campi dell'elemento del nuovo elemento per l'inserimento in tabellaHash
			strcpy(username, strtok(msx -> msg, ":"));
			strcpy(name, strtok(NULL, ":"));
			strcpy(email, strtok(NULL, "\0"));
			pthread_mutex_lock(&hashmux);
			elemento = ricercaUsername(Table, username);

			//Controllo se l'utente non esiste
			if(elemento == NULL) {

				//Aggiorno la tabellaHash
				inserisciHashtable(Table, username, name, email, descr);
				pthread_mutex_unlock(&hashmux);

				//Compilo il messaggio di risposta
				resettaMessaggio(msx);
				msx -> type = MSG_OK;

				//Scrivo nel log l'evento
				pthread_mutex_lock(&logmux);
				timestamp(timestring);
				strcpy(tobewrite, timestring);
				strcat(tobewrite, ":");
				strcat(tobewrite, "Register and login:");
				strcat(tobewrite, username);
				strcat(tobewrite, "\n");
				result = write(logdescr, tobewrite, strlen(tobewrite));
				pthread_mutex_unlock(&logmux);

			//Se l'utente esiste, allora compilo il messaggio di errore
			} else {

				//Compilo il messaggio di risposta
				pthread_mutex_unlock(&hashmux);
				resettaMessaggio(msx);
				msx -> type = MSG_ERROR;
				msx -> msglen = strlen("Impossibile registrarsi, utente già registrato...");
				msx -> msg = malloc(msx -> msglen + 1);
				strcpy(msx -> msg, "Impossibile registrarsi, utente già registrato...");
				impacchettaMessaggio(msx, temp);

				//Invio la risposta al client
				size = strlen(temp);
				result = write(descr, &size, sizeof(int));
				result = write(descr, temp, strlen(temp) + 1);

				//Diminuisco il numero di thread attivi, chiudo il socket e termino il thread
				numThreadAttivi--;
				close(descr);
				pthread_exit(NULL);
			}

			//Preparo il messaggio per l'invio
			free(temp);
			temp = malloc(grandezzaMessaggio(msx));
			impacchettaMessaggio(msx, temp);
			size = strlen(temp);

			//Invio il messaggio
			if((write(descr, &size, sizeof(int)) < 0) || (write(descr, temp, strlen(temp) + 1) < 0))

				//Eseguo la procedura di disconnessione se ci sono stati errori
				disconnesso(descr);
			break;

		/* ****************************************************************** *
		 * Determino il tipo di messaggio letto (messaggio a singolo)         *
		 * ****************************************************************** */

		//Caso di invio a destinatario singolo
		case MSG_SINGLE:

			//Controllo se il destinatario esiste
			pthread_mutex_lock(&hashmux);
			elemento = ricercaUsername(Table, msx -> receiver);
			strcpy(username, msx -> receiver);
			//fprintf(stderr, "LETTO %d\n", elemento -> descr);

			//Controllo se l'utente non esiste
			if(elemento == NULL) {

				//Compilo il messaggio di risposta
				pthread_mutex_unlock(&hashmux);
				resettaMessaggio(msx);
				msx -> type = MSG_ERROR;
				msx -> msglen = strlen("Impossibile inviare messaggio, utente inesistente...");
				msx -> msg = malloc(msx -> msglen + 1);
				strcpy(msx -> msg, "Impossibile inviare messaggio, utente inesistente...");

			//Se l'utente esiste controllo che sia connesso
			} else if(elemento -> descr == -1) {

				//Compilo il messaggio di risposta
				pthread_mutex_unlock(&hashmux);
				resettaMessaggio(msx);
				msx -> type = MSG_ERROR;
				msx -> msglen = strlen("Impossibile inviare messaggio, utente offline...");
				msx -> msg = malloc(msx -> msglen + 1);
				strcpy(msx -> msg, "Impossibile inviare messaggio, utente offline...");

			//Se l'utente è online si può inviare il messaggio
			} else {

				//Ottengo l'elemento inerente al descrittore del mittente
				elemento = ricercaDescrittore(Table, descr);
				msx -> sender = malloc(sizeof(char) * strlen(elemento -> username));
				strcpy(msx -> sender, elemento -> username);
				pthread_mutex_unlock(&hashmux);

				//Scrivo nel log l'evento
				pthread_mutex_lock(&logmux);
				timestamp(timestring);
				strcpy(tobewrite, timestring);
				strcat(tobewrite, ":");
				strcat(tobewrite, msx -> sender);
				strcat(tobewrite, ":");
				strcat(tobewrite, msx -> receiver);
				strcat(tobewrite, ":");
				strcat(tobewrite, msx -> msg);
				strcat(tobewrite, "\n");
				result = write(logdescr, tobewrite, strlen(tobewrite));
				pthread_mutex_unlock(&logmux);

				//Preparo la scrittura del messaggio nel buffer
				temp = malloc(grandezzaMessaggio(msx));
				impacchettaMessaggio(msx, temp);

				//Controllo che il buffer non sia pieno
				pthread_mutex_lock(&buffer -> mux);
				while(buffer -> cont == BUFFER_SIZE)
					pthread_cond_wait(&buffer->full, &buffer->mux);

				//Scrivo il messaggio nel buffer
				buffer -> space[buffer -> writepos] = malloc(sizeof(char) * strlen(temp));
				strcpy(buffer -> space[buffer -> writepos], temp);
				buffer -> cont++;
				buffer -> writepos++;

				//Gestione circolare posizione di scrittura
				if(buffer -> writepos >= BUFFER_SIZE)
					buffer -> writepos = 0;

				//Risveglio del dispatcher se in attesa per poter leggere il buffer
				pthread_cond_signal(&buffer -> empty);
				pthread_mutex_unlock(&buffer -> mux);

				//Compilo il messaggio di risposta
				resettaMessaggio(msx);
				msx -> type = MSG_OK;
			}

			//Preparo il messaggio per l'invio
			free(temp);
			temp = malloc(grandezzaMessaggio(msx));
			impacchettaMessaggio(msx, temp);
			size = strlen(temp);

			//Invio il messaggio
			if((write(descr, &size, sizeof(int)) < 0) || (write(descr, temp, strlen(temp) + 1) < 0))

				//Eseguo la procedura di disconnessione se ci sono stati errori
				disconnesso(descr);
			break;

		/* ****************************************************************** *
		 * Determino il tipo di messaggio letto (messaggio broadcast)         *
		 * ****************************************************************** */

		//Caso di invio a destinatario singolo
		case MSG_BRDCAST:

			//Leggo la lista di utenti connessi dalla tabellaHash
			list = malloc(sizeof(char));
			pthread_mutex_lock(&hashmux);
			listaUtentiOnline(Table, list, numThreadAttivi);
			elemento = ricercaDescrittore(Table, descr);
			msx -> sender = malloc(sizeof(char) * (strlen(elemento -> username)));
			strcpy(msx -> sender, elemento -> username);
			pthread_mutex_unlock(&hashmux);

			//Scrivo nel log l'evento
			pthread_mutex_lock(&logmux);
			strcpy(receiver, strtok(list, ":"));
			for(i = 0; i < numThreadAttivi; i++) {

				//Ottengo il timestamp e scrivo nel file di log
				timestamp(timestring);
				strcpy(tobewrite, timestring);
				strcat(tobewrite, ":");
				strcat(tobewrite, msx -> sender);
				strcat(tobewrite, ":");
				strcat(tobewrite, receiver);
				strcat(tobewrite, ":");
				strcat(tobewrite, msx -> msg);
				strcat(tobewrite, "\n");
				result = write(logdescr, tobewrite, strlen(tobewrite));

				//Ricavo il nome dell'utente
				temp = strtok(NULL, ":");
				if(temp != NULL)
					strcpy(receiver, temp);
			}
			pthread_mutex_unlock(&logmux);

			//Preparo la scrittura del messaggio nel buffer
			free(list);
			free(temp);
			temp = malloc(grandezzaMessaggio(msx));
			impacchettaMessaggio(msx, temp);

			//Controllo che il buffer non sia pieno
			pthread_mutex_lock(&buffer -> mux);
			while(buffer -> cont == BUFFER_SIZE)
				pthread_cond_wait(&buffer->full, &buffer->mux);

			//Scrivo il messaggio nel buffer
			buffer -> space[buffer -> writepos] = malloc(sizeof(char) * strlen(temp));
			strcpy(buffer -> space[buffer -> writepos], temp);
			buffer -> cont++;
			buffer -> writepos++;

			//Gestione circolare posizione di scrittura
			if(buffer -> writepos >= BUFFER_SIZE)
				buffer -> writepos = 0;

			//Risveglio del dispatcher se in attesa per poter leggere il buffer
			pthread_cond_signal(&buffer -> empty);
			pthread_mutex_unlock(&buffer -> mux);

			//Compilo il messaggio di risposta
			resettaMessaggio(msx);
			msx -> type = MSG_OK;

			//Preparo il messaggio per l'invio
			free(temp);
			temp = malloc(grandezzaMessaggio(msx));
			impacchettaMessaggio(msx, temp);
			size = strlen(temp);

			//Invio il messaggio
			if((write(descr, &size, sizeof(int)) < 0) || (write(descr, temp, strlen(temp) + 1) < 0))

				//Eseguo la procedura di disconnessione se ci sono stati errori
				disconnesso(descr);
			break;

		/* ****************************************************************** *
		 * Determino il tipo di messaggio letto (messaggio listing)           *
		 * ****************************************************************** */

		//Caso di listing
		case MSG_LIST:

			//Leggo la lista di utenti connessi dalla tabellaHash
			free(list);
			list = malloc(sizeof(char));
			pthread_mutex_lock(&hashmux);
			listaUtentiOnline(Table, list, numThreadAttivi);
			pthread_mutex_unlock(&hashmux);

			//Preparo il messaggio per l'invio
			resettaMessaggio(msx);
			msx -> type = MSG_LIST;
			msx -> msglen = strlen(list);
			msx -> msg = malloc(sizeof(char) * msx -> msglen);
			strcpy(msx -> msg, list);
			free(temp);
			temp = malloc(grandezzaMessaggio(msx));
			impacchettaMessaggio(msx, temp);
			size = strlen(temp);

			//Invio il messaggio
			if((write(descr, &size, sizeof(int)) < 0) || (write(descr, temp, strlen(temp) + 1) < 0))

				//Eseguo la procedura di disconnessione se ci sono stati errori
				disconnesso(descr);
			break;

		/* ****************************************************************** *
		 * Determino il tipo di messaggio letto (messaggio logout)            *
		 * ****************************************************************** */

		//Caso di logout
		case MSG_LOGOUT:

			//Preparo il messaggio per essere inviato
			resettaMessaggio(msx);
			msx -> type = MSG_OK;
			free(temp);
			temp = malloc(grandezzaMessaggio(msx));
			impacchettaMessaggio(msx, temp);
			size = strlen(temp);

			//Invio il messaggio
			result = write(descr, &size, sizeof(int));
			result = write(descr, temp, strlen(temp) + 1);

			//Eseguo la procedura di disconnessione
			disconnesso(descr);
			break;
		}
	}

	//Diminuisco il numero di thread attivi, chiudo il socket e termino il thread
	numThreadAttivi--;
	close(descr);
	pthread_exit(NULL);
}

/*
 * Procedura che ricava il timestamp attuale in formato testuale
 *
 * @param Vettore di caratteri in cui verrà scritto il timestamp
 */
void timestamp(char timestring[]) {

	//Ricavo il timestamp attuale in intero
	time_t t = time(NULL);

	//Scrivo il timestamp in formato testuale all'interno di timestring
	ctime_r (&t, timestring);

	//Rimuovo il carattere "/n" dalla fine della stringa
	timestring[strlen(timestring)-1] = '\0';
}

/*
 * Procedura che aggiorna l'hashtable e chiude il socket, utilizzata nel caso
 * in cui il client sia crashato
 */
void disconnesso(int descr) {

	//Variabili utili per gestire la disconnessione
	char username[MAX_CHAR], tobewrite[MAX_CHAR * 2];
	int result = 0;
	hashcell * elemento;

	//Sopprime warning durante la compilazione
	result = result;

	//Accedo alla hashtable
	pthread_mutex_lock(&hashmux);
	elemento = ricercaDescrittore(Table, descr);
	strcpy(username, elemento -> username);
	elemento -> descr = -1;
	pthread_mutex_unlock(&hashmux);

	//Scrivo nel log l'evento
	pthread_mutex_lock(&logmux);
	timestamp(timestring);
	strcpy(tobewrite, timestring);
	strcat(tobewrite, ":");
	strcat(tobewrite, "logout:");
	strcat(tobewrite, username);
	strcat(tobewrite, "\n");
	result = write(logdescr, tobewrite, strlen(tobewrite));
	pthread_mutex_unlock(&logmux);

	//Diminuisco il numero di thread attivi, chiudo il socket e termino il thread
	numThreadAttivi--;
	close(descr);
	pthread_exit(NULL);
}

/*
 * Procedura per la gestione dei segnali di terminazione
 *
 * @param Intero che contiene il segnale inserito da console
 */
void gestoreSegnali(int sig) {

	//Variabili utili per la gestione dei socket
	int i, result = 0, * list;

	//Stringa per il salvataggio delle cose da scrivere nel file
	char tobewrite[MAX_CHAR * 8];

	//Sopprime warning durante la compilazione
	result = result;

	//Controllo il segnale in input
	if(sig == SIGTERM || sig == SIGINT) {

		//Scrivo la tabella hash nel file
		scriviFile(Table, userfile);

		//Chiudo tutti i socket
		list = malloc(sizeof(int) * numThreadAttivi);
		pthread_mutex_lock(&hashmux);
		listaDescrittoriOnline(Table, list, numThreadAttivi);
		pthread_mutex_unlock(&hashmux);

		//Ottengo la lista dei socket
		for(i = 0; i < numThreadAttivi; i++) {

			//Chiudo i socket uno per uno
			close(list[i]);
		}

		//Scrivo lo spegnimento del server nel log
		strcpy(tobewrite, "****************************************************\n");
		result = write(logdescr, tobewrite, strlen(tobewrite));
		strcpy(tobewrite, "** Chat Server stopped @ ");
		result = write(logdescr, tobewrite, strlen(tobewrite));
		result = write(logdescr, timestring, strlen(timestring));
		strcpy(tobewrite, " **\n");
		result = write(logdescr, tobewrite, strlen(tobewrite));
		strcpy(tobewrite, "****************************************************\n");
		result = write(logdescr, tobewrite, strlen(tobewrite));

		//Chiudo il file di log
		close(logdescr);

		//Chiudo il socket master
		close(masterdescr);

		//Eseguo procedura di arresto del server impostando go a 0
		go = 0;

		//Termino il dispatcher (senza questo il processo non viene chiuso,
		//poichè il dispatcher è bloccato in attesa del buffer)
		pthread_kill(dispatcher_tid, SIGKILL);
	}
}
